package com.study.javaOther.recommend_system;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class OfflineRecommenderSystem {
    public static void main(String[] args) throws Exception {
        BufferedReader in = new BufferedReader(new FileReader(
                "resources/ratings_train_test.txt"));
        Map<Integer, ArrayList<Item>> item_user = new HashMap<Integer, ArrayList<Item>>();
        Map<Integer, ArrayList<Item>> user_item = new HashMap<Integer, ArrayList<Item>>();
        ArrayList<Integer> movies = new ArrayList<>();
        while (in.ready()) {
            String str = in.readLine();
            getValue(user_item, item_user, str, movies);
        }
        System.out.println("item_user加载完成");
        System.out.println("user_item加载完成");
        in.close();
        //getMoSim(item_user,user_item);
        for (Integer mo : movies) {
            getSim(mo, item_user, user_item);
        }
    }

    /**
     * 写入文件
     *
     * @param item_user
     * @param user_item
     * @throws Exception
     */
    public static void getMoSim(Map<Integer, ArrayList<Item>> item_user,
                                Map<Integer, ArrayList<Item>> user_item) throws Exception {

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(
                "item_user.txt"));
        ObjectOutputStream out1 = new ObjectOutputStream(new FileOutputStream(
                "user_item.txt"));
        out.writeObject(item_user);
        out1.writeObject(user_item);

        out.close();
        out1.close();
        System.out.println("item_user写入文件");
        System.out.println("user_item写入文件");
    }

    /**
     * 根据输入一行来获取对应的Value，并获取已经参加过评分的movie
     *
     * @param item_user Map
     * @param input     输入的一行代码
     */
    public static void getValue(Map<Integer, ArrayList<Item>> user_item,
                                Map<Integer, ArrayList<Item>> item_user, String input,
                                ArrayList<Integer> movies) {
        ArrayList<Item> arr = new ArrayList<>();
        String[] str = input.split("::");
        int temp = Integer.parseInt(str[1]);
        if (item_user.containsKey(temp)) {
            arr = item_user.get(temp);
        }
        arr.add(new Item(Integer.parseInt(str[0]), Float.parseFloat(str[2])));
        item_user.put(temp, arr);

        ArrayList<Item> arr1 = new ArrayList<>();
        String[] str1 = input.split("::");
        int temp1 = Integer.parseInt(str1[0]);
        if (user_item.containsKey(temp1)) {
            arr1 = user_item.get(temp1);
        }
        arr1.add(new Item(Integer.parseInt(str1[1]), Float.parseFloat(str1[2])));
        user_item.put(temp1, arr1);

        if (movies != null) {
            int movie = Integer.parseInt(str[1]);
            if (!movies.contains(movie))
                movies.add(movie);
        }

    }

    /**
     * 根据评分计算相似度
     *
     * @param va
     * @param vb
     * @return
     */
    public static float caclSim(ArrayList<Float> va, ArrayList<Float> vb) {
        // 如果向量维度不相等，则不能计算，函数退出
        if (va.size() != vb.size()) {
            return 0;
        }

        int size = va.size();
        float simVal = 0;
        float num = 0;
        float den = 0;
        float A = 0, B = 0;

        for (int i = 0; i < size; i++) {
            num += va.get(i) * vb.get(i);
            A += va.get(i) * va.get(i);
            B += vb.get(i) * vb.get(i);
        }
        den = (float) (Math.sqrt(A) * Math.sqrt(B));

        simVal = num / den;
        return simVal;
    }

    /**
     * 判断users是否包含user（相同的id）
     *
     * @param users 看过电影2的所有用户及其评分
     * @param user  用户
     */
    public static boolean containsUser(ArrayList<Item> users, Item user) {
        for (Item item : users) {
            if (item.getId() == user.getId())
                return true;
        }
        return false;
    }

    /**
     * 获取mo电影的相似度并存放在sim_table.txt中
     *
     * @param mo
     * @param item_user
     * @param user_item
     * @throws Exception
     * @throws IOException
     */
    public static void getSim(int mo, Map<Integer, ArrayList<Item>> item_user,
                              Map<Integer, ArrayList<Item>> user_item) throws Exception,
            IOException {
        ArrayList<Item> users = item_user.get(mo);
        ArrayList<Item> users1 = new ArrayList<>();
        ArrayList<Item> MovieSim = new ArrayList<>();// 这部电影的相似度
        ArrayList<Integer> movies = getSimMoive(mo, item_user, user_item);
        FileWriter write = new FileWriter(new File("sim_table.txt"), true);
        for (Integer integer : movies) {
            ArrayList<Float> va = new ArrayList<Float>();// 对电影1的所有评分
            ArrayList<Float> vb = new ArrayList<Float>();// 对电影2的所有评分
            users1 = item_user.get(integer);// 放的是对2进行评分的用户和它的评分
            for (Item rating : users) {
                if (containsUser(users1, rating))
                    va.add(rating.getMovieRating());
            }
            for (Item rating : users1) {
                if (containsUser(users, rating))
                    vb.add(rating.getMovieRating());
            }
            float sim = -1f;
            if (va.size() > 2) {
                sim = caclSim(va, vb);
                MovieSim.add(new Item(integer, sim));
            }
        }
        if (MovieSim.size() > 0) {
            write.write(mo + "\t");
            for (Item ite : MovieSim)
                write.write(ite.getId() + "," + ite.getMovieRating() + "\t");
            write.write("\n");
        }
        write.close();
    }

    /**
     * 获取mo的相似邻居
     *
     * @param mo
     * @param item_user
     * @param user_item
     * @return
     */
    public static ArrayList<Integer> getSimMoive(int mo,
                                                 Map<Integer, ArrayList<Item>> item_user,
                                                 Map<Integer, ArrayList<Item>> user_item) {
        ArrayList<Item> users = new ArrayList<>();
        ArrayList<Integer> movies = new ArrayList<>();
        users = item_user.get(mo);// 放的是对1进行评分的用户和它的评分
        for (Item user : users) {
            ArrayList<Item> m = user_item.get(user.getId());
            for (Item item : m) {
                if (!movies.contains(item.getId()) && item.getId() != mo)
                    movies.add(item.getId());
            }
        }
        return movies;
    }
}
